package com.android.monee.domain;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import com.android.monee.data.access.BudgetDao;
import com.android.monee.data.access.DaoFactory;
import com.android.monee.data.access.DaoListener;
import com.android.monee.data.access.MensualityDao;
import com.android.monee.data.access.inMemory.ObservableDao;
import com.android.monee.data.model.Budget;
import com.android.monee.data.model.Budget.BudgetState;
import com.android.monee.data.model.Mensuality;
import com.android.monee.data.model.Mensuality.MensualityState;

public class BudgetService extends Budget {

	private MensNbCalc mensNbCalc;
	private MensualityCalculator mensCalc;
	
	public BudgetService (Budget budget){
		super(budget);
		construtor();
	}
	
	public BudgetService(){
		construtor();
	}
	
	public BudgetService (String name, float totalAmount, float provAmount, Date begin, Date term){
		this.setName(name);
		this.setTotalAmount(totalAmount);
		this.setInitialAmount(provAmount);
		this.setBegin(begin);
		this.setTerm(term);
		construtor();
	}
	
	private void construtor(){
		this.mensNbCalc = MensNbCalcFactory.getMensNbCalc();
		this.state = BudgetState.RUNNING;
		updateMensCal();
	}
	
	private void updateMensCal(){
		switch(getState()){
		case RUNNING:
			this.mensCalc = new RunningMensCalculator(this);
			break;
		case PAUSED:
			this.mensCalc = new PausedMensCalculator(this);
			break;
		}
	}
	
	@Override
	public void setState(BudgetState state) {
		super.setState(state);
		updateMensCal();
	}

	public float getRemainingAmount(){
		return getTotalAmount()-getProvAmount();
	}
	
	public float getMensAmount(){
		int remainingNbMens = getRemainingNbMens();
		if(remainingNbMens>0)
			return getRemainingAmount()/getRemainingNbMens();
		else
			return 0;
	}
	
	public int getTotalNbMens() {
		return this.mensNbCalc.getNbMensuality(this);
	}
	
	public int getDoneNbMens() {
		return getDoneMensualities().size();
	}
	
	public int getRemainingNbMens() {
		return getTotalNbMens()-getDoneNbMens();
	}
	
	public float getProvAmount(){
		List<MensualityService> doneMens = getDoneMensualities();
		float sum = 0;
		for(MensualityService mens : doneMens){
			sum += mens.getAmount();
		}
		return getInitialAmount()+sum;
	}
	
	public Date getDateForMensuality(int mensualityNumber){
		Calendar cal = Calendar.getInstance();
		cal.setTime(this.getBegin());
		int beginMonth = cal.get(Calendar.MONTH);
		int beginYear = cal.get(Calendar.YEAR);
		
		cal.clear();
		cal.set(Calendar.YEAR, beginYear);
		cal.set(Calendar.DAY_OF_MONTH, 1);
		cal.set(Calendar.MONTH, beginMonth);
		cal.add(Calendar.MONTH, mensualityNumber-1);
		
		return cal.getTime();
	}

	public List<MensualityService> getDoneMensualities() {
		return MensualityService.findByBudgetAndState(this, MensualityState.DONE);
	}
	
	public List<MensualityService> getCancelledMensualities() {
		return MensualityService.findByBudgetAndState(this, MensualityState.CANCELLED);
	}
	
	public Set<Integer> getUndoneMensNumbers(){
		SortedSet<Integer> allMensNumbers = new TreeSet<Integer>();
		for(int i = 1 ; i <= this.getTotalNbMens() ; i++){
			allMensNumbers.add(i);
		}
		
		List<MensualityService> doneMensualities = this.getDoneMensualities();
		List<MensualityService> cancelledMensualities = this.getCancelledMensualities();
		for(MensualityService mens : doneMensualities){
			allMensNumbers.remove(mens.getNumber());
		}
		
		for(MensualityService mens : cancelledMensualities){
			allMensNumbers.remove(mens.getNumber());
		}
		
		return allMensNumbers;
	}
	
	public List<Mensuality> calcNextMensualities(){
		return mensCalc.calcNextMensualities();
	}
	
	//TODO revoir l'implémentation de la persistence des données, le budget peut me créer une mensualité mais 
	//quand se fait l'insertion en bdd ? ici, à l'insertion/update du budget ?
	//Ce qui est dommage ici c'est de ne pas pouvoir travailler en 'mémoire' car il faut que le budget soit persisté pour avoir un id.
	public Mensuality createMensuality() {
		MensualityDao mensDao = DaoFactory.getMensualityDao();
		Mensuality m = new Mensuality();
		m.setBudgetId(this.getId());
		m.setState(MensualityState.UNDONE);
		mensDao.insert(m);
		return m;
	}
	
	public static Float calcTotalMensAmount(Collection<? extends BudgetService> budgets) {
		float sum = 0;
		for (BudgetService budget : budgets) {
			sum += budget.getMensAmount();
		}
		return sum;
	}

	public static Float calcTotalProvAmount(Collection<? extends BudgetService> budgets) {
		float sum = 0;
		for (BudgetService budget : budgets) {
			sum += budget.getProvAmount();
		}
		return sum;
	}
	
	public static <T extends Budget> T findInCollectionById (Integer budgetId, Collection<T> budgetCollection){
		T res = null;
		for (T budget : budgetCollection){
			if(budget.getId().equals(budgetId))
				return budget;
		}
		return res;
	}
	
	public static List<BudgetService> newCollection(Collection<? extends Budget> budgets){
		List<BudgetService> newCol = new ArrayList<BudgetService>();
		for(Budget budget : budgets){
			newCol.add(new BudgetService(budget));
		}
		return newCol;
	}
	
	public static List<BudgetService> findAll() {
		BudgetDao dao = DaoFactory.getBudgetDao();
		return newCollection(dao.findAll());
	}
	
	public static BudgetService findById(Integer id){
		BudgetDao dao = DaoFactory.getBudgetDao();
		return new BudgetService(dao.findById(id));
	}
	
	public static void insert(Budget budget){
		BudgetDao dao = DaoFactory.getBudgetDao();
		dao.insert(budget);
	}

	public static void update(Budget budget){
		BudgetDao dao = DaoFactory.getBudgetDao();
		dao.update(budget);
	}
	
	public static void delete(Budget budget){
		BudgetDao dao = DaoFactory.getBudgetDao();
		dao.delete(budget.getId());
	}
	
	public static boolean addBudgetDaoListener (DaoListener<Budget, Integer> listener){
		BudgetDao dao = DaoFactory.getBudgetDao();
		if(dao instanceof ObservableDao){
			ObservableDao<Budget, Integer> observableDao = (ObservableDao<Budget, Integer>) dao;
			observableDao.addDaoListener(listener);
			return true;
		}
		else 
			return  false;
	}
	
	public static boolean deleteBudgetDaoListener (DaoListener<Budget, Integer> listener){
		BudgetDao dao = DaoFactory.getBudgetDao();
		if(dao instanceof ObservableDao){
			ObservableDao<Budget, Integer> observableDao = (ObservableDao<Budget, Integer>) dao;
			observableDao.deleteDaoListener(listener);
			return true;
		}
		else 
			return false;
	}
}
